package storage.buffer;

import java.util.HashMap;

import exceptions.DBAllocateException;
import exceptions.DBBufferFull;
import exceptions.DBCannotUnpin;
import exceptions.DBFullException;
import exceptions.DBInvalidPageIDException;
import exceptions.DBReadPageException;
import exceptions.DBUnpinPageNotExists;
import exceptions.DBWritePageException;

import Common.GlobalConst;
import Common.GoggleDB;

import storage.data.Page;
import storage.data.PageID;


public class BufferMgr  {
	
	Slot [] pool ; 
	byte [][] buff ;  
	HashMap < PageID, Integer >map = new HashMap < PageID, Integer > ()  ;
	Replacer replacer ; 
	static final int BUFFER_SIZE = GlobalConst.BUFFERPOOLSIZE ;
		
	public BufferMgr () {
		pool = new Slot [ BUFFER_SIZE ] ;
		for ( int i = 0 ; i < BUFFER_SIZE ; i ++ ) pool [ i ] = new Slot () ; 
		buff = new byte [ BUFFER_SIZE ][ ] ;
		replacer = new SecondChance ( this );
	}
	
	
	/**
	 * Note that the newly allocated paged are initially pinned
	 * So caller should unpin it after using it.
	 * @return
	 * @throws Exception
	 */
	public synchronized PageID  allocatePage( int howmany) throws  DBFullException, DBAllocateException {
		PageID pid ;
		pid = GoggleDB.storageInterface.allocatePage( howmany ) ;
		return pid ; 
	}
		
	public  synchronized int deallocatePage( PageID pageToDel, int howmany )  throws Exception {
		GoggleDB.storageInterface.deallocatePage( pageToDel, howmany ) ;
		return 0 ;
	}
	
	
	/**
	 * What this function do is to free the page that has been pinned but no longer needed in buffer.
	 * @param pid  the PageID of the page that no longer needed in buffer 
	 */
	public void freePage ( PageID pid ) {
		/* 
		 * Waited to be implemented 
		 */
	}
	
	
	/**
	 * 
	 * @param id: pageID
	 * @param aPage: the page to buffer
	 * @param empty: This parameter optimize loading. 
	 * 			When you know a page is empty, e.g. loading a new page, you set empty to true 
	 * 			and then no disk IO accually occurs.
	 * @return 
	 * @throws Exception
	 */
	public synchronized int pinPage(PageID pid, Page aPage, boolean empty ) 
		throws DBReadPageException, DBWritePageException, DBBufferFull, DBInvalidPageIDException {
		if ( pid.pageId < 0 || 
				( GoggleDB.storageInterface.getNumPages() != 0 && pid.pageId >= GoggleDB.storageInterface.getNumPages()) ) 
			throw new DBInvalidPageIDException( null, pid ) ; 
		Integer slotNo = map.get( pid ) ;
		if ( slotNo == null ) {   // the page to load is not in the buffer
			int freeSlot = replacer.pickPage() ;
			slotNo = freeSlot ; 
			if ( pool [ freeSlot ].pid.pageId != -1 )  map.remove( pool [ freeSlot ].pid ) ;
			pool [ freeSlot ].pid.pageId = pid.pageId ; 
			map.put ( pool [ freeSlot ].pid , freeSlot ) ;
			if ( buff [ freeSlot ] == null ) buff [ freeSlot ] = new byte [ GlobalConst.PAGESIZE ] ; 
			Page tempPage = new Page ( buff [ freeSlot ]) ;
			if ( ! empty ) readPage ( pid, tempPage ) ;
			else java.util.Arrays.fill( buff [ freeSlot ], (byte)0 ) ;
		}
		else {  // it's in the buffer
			pool[ slotNo ].pin() ; 
		}
		aPage.setData( buff [ slotNo ]) ; 
		return 0 ;
	}
	
	public synchronized void unpinPage(PageID pid, boolean dirty ) throws DBUnpinPageNotExists, DBCannotUnpin {
		Integer slotNo = map.get( pid ) ;
		if ( slotNo != null ) {
			pool [ slotNo ].unpin() ;
			pool [ slotNo ].dirty = pool [ slotNo ].dirty || dirty ;
		}
		else throw new DBUnpinPageNotExists ( null, pid ) ; 
	}
 

	void readPage ( PageID pid, Page aPage ) throws DBReadPageException {
		try {
			GoggleDB.storageInterface.readPage( pid , aPage ) ;
		} catch ( Exception e ){
			throw new DBReadPageException ( e , pid ) ; 
		}
	}
	
	int writePage(PageID pid, Page aPage ) throws DBWritePageException {
		try {
			GoggleDB.storageInterface.writePage( pid , aPage ) ;
		} catch ( Exception e ){
			throw new DBWritePageException( e , pid ) ; 
		}
		return 0;
	}
	
	public synchronized void flushAll () throws Exception {
		Page aPage = new Page ( false ) ; 
		for ( int i = 0 ; i < BUFFER_SIZE ; i ++ ) 
			if ( pool [ i ].pid != Page.INVALID_PAGE && pool [ i ].dirty ) {
				aPage.setData(  buff [ i ] ) ; 
				writePage( pool [ i ].pid , aPage ) ;
				aPage.setDirty( false ) ; 
				pool [ i ].dirty = false ; 
			}
	}
	
	public synchronized void flush ( PageID pid ) throws Exception {
		Page aPage = new Page () ;
		Integer slot = map.get( pid ) ;
		if ( slot == null) return ;
		aPage.setData( buff[ slot ]) ; 
		writePage ( pid, aPage ) ;
		pool [ slot ].dirty = false ;
	}
	
	public void printBuffer () {
		for ( int i = 0 ; i < BUFFER_SIZE ; i ++ ) 
			System.out.print ( pool [ i ].pid.pageId + ":"  + pool[i].count +" ") ;
		System.out.println () ;
	}
	
}
